Understanding Object Serialization and Deserialization in Java
Course: Core Java Training
Topic: Understanding Object Serialization and Deserialization
Course Link: Core Java Training Course
Demo Link: Register for a Free Course Demo
Website: JustAcademy
1. Introduction to Serialization and Deserialization
Java applications often need to save objects, transfer data between systems, or preserve application state for later use. Serialization and deserialization help Java programs perform these operations by converting objects into a transferable or storable representation and reconstructing them when needed.
Serialization converts an object's state into a byte stream. The byte stream can be written to a file, sent over a network, or stored for later processing.
Deserialization is the reverse process. It reads a serialized byte stream and reconstructs an object from it.
For example, an application may serialize a Student object and save it to a file. Later, the application can deserialize the file to retrieve the student's stored information.
2. What Is Object Serialization?
Object serialization is the process of converting an object's serializable state into a sequence of bytes. Java provides object-stream classes to perform this operation.
To make an ordinary Java class eligible for default serialization, the class generally implements the java.io.Serializable marker interface.
Key Characteristics
- Converts an object's state into a byte stream.
- Supports saving objects to files and transferring them between Java applications.
- Uses
ObjectOutputStream to write objects.
- Works with classes that implement
Serializable, subject to serialization rules.
- Can preserve references between objects in an object graph.
3. What Is Object Deserialization?
Deserialization reconstructs an object by reading a previously serialized byte stream. Java provides ObjectInputStream for this purpose.
The class definition must be available when the object is reconstructed, and the class must be compatible with the serialized data. Deserialization can fail if the stream is corrupted, the class cannot be found, or compatibility requirements are not met.
Key Characteristics
- Reads serialized data from a source such as a file.
- Reconstructs the object's serialized state.
- Uses
ObjectInputStream and its readObject() method.
- Can throw exceptions such as
IOException and ClassNotFoundException.
- Requires careful validation when data comes from an untrusted source.
4. Serialization and Deserialization: Key Differences
| Basis |
Serialization |
Deserialization |
| Meaning |
Converts object state into bytes. |
Reconstructs an object from bytes. |
| Direction |
Object to byte stream. |
Byte stream to object. |
| Main class |
ObjectOutputStream |
ObjectInputStream |
| Main method |
writeObject() |
readObject() |
| Common use |
Saving or transmitting object state. |
Loading previously saved object state. |
| Typical exceptions |
IOException |
IOException, ClassNotFoundException |
5. Understanding the Serializable Interface
Serializable is a marker interface in the java.io package. It does not define methods that a class must implement. Instead, it indicates that Java's default serialization mechanism may serialize instances of that class.
import java.io.Serializable;
class Student implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
In this example, the Student class implements Serializable, allowing its eligible instance fields to be serialized using Java's default mechanism.
Why Is Serializable Called a Marker Interface?
A marker interface communicates metadata about a class without requiring the class to implement specific methods. The Java serialization mechanism checks whether the class implements this interface.
6. Understanding serialVersionUID
serialVersionUID is a version identifier associated with a serializable class. During deserialization, Java compares the identifier in the stream with the identifier of the local class to help determine compatibility.
private static final long serialVersionUID = 1L;
Declaring it explicitly helps avoid relying on a compiler-generated value that can change when class details change.
Important Points
- It is usually declared as a
private static final long field.
- Compatible class changes may retain the same identifier when the serialized form remains compatible.
- Incompatible changes may require a versioning strategy or a new identifier.
- Matching identifiers do not guarantee that every class change is safe or semantically compatible.
7. Important Classes Used in Serialization
| Class or Interface |
Purpose |
Serializable |
Marks a class as eligible for default Java serialization. |
ObjectOutputStream |
Writes objects to an output stream. |
ObjectInputStream |
Reads and reconstructs objects from an input stream. |
FileOutputStream |
Writes bytes to a file. |
FileInputStream |
Reads bytes from a file. |
Externalizable |
Provides a custom serialization mechanism through writeExternal() and readExternal(). |
8. How Serialization Works Step by Step
- Create a class that implements
Serializable.
- Create an object of that class and initialize its fields.
- Create an output stream, such as
FileOutputStream.
- Wrap it in an
ObjectOutputStream.
- Call
writeObject() to serialize the object.
- Close the stream or use try-with-resources to release it automatically.
- The resulting byte stream can be stored or transferred.
9. How Deserialization Works Step by Step
- Obtain the serialized byte stream from a file or another source.
- Create an input stream such as
FileInputStream.
- Wrap it in an
ObjectInputStream.
- Call
readObject() to reconstruct the object.
- Cast the returned value to the expected type after appropriate validation.
- Use the reconstructed object's data.
- Close the stream or use try-with-resources.
10. Complete Example: Serialization and Deserialization
The following example creates a Student object, saves it to a file, and then reads it back.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Student implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private double marks;
public Student(int id, String name, double marks) {
this.id = id;
this.name = name;
this.marks = marks;
}
@Override
public String toString() {
return "Student{id=" + id
+ ", name='" + name + '\''
+ ", marks=" + marks + '}';
}
}
public class SerializationDemo {
public static void main(String[] args) {
Student student = new Student(101, "Rahul", 87.5);
// Serialization
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("student.ser"))) {
out.writeObject(student);
System.out.println("Object serialized successfully.");
} catch (IOException e) {
System.out.println("Serialization failed: " + e.getMessage());
return;
}
// Deserialization
try (ObjectInputStream in = new ObjectInputStream(
new FileInputStream("student.ser"))) {
Object value = in.readObject();
if (value instanceof Student) {
Student restoredStudent = (Student) value;
System.out.println("Deserialized object:");
System.out.println(restoredStudent);
} else {
System.out.println("Unexpected object type.");
}
} catch (IOException | ClassNotFoundException e) {
System.out.println("Deserialization failed: " + e.getMessage());
}
}
}
Expected Output
Object serialized successfully.
Deserialized object:
Student{id=101, name='Rahul', marks=87.5}
Explanation: The first stream writes the Student object to student.ser. The second stream reads the stored object. The reconstructed object's fields are then displayed through the overridden toString() method.
Note: The file is a binary serialization stream, not a plain-text document. Its contents should be read with compatible Java object-stream APIs rather than a text editor.
11. Using transient Fields
The transient keyword tells Java's default serialization mechanism not to serialize a particular instance field. After default deserialization, such a field receives its default value unless the class restores it through custom logic.
import java.io.Serializable;
class UserAccount implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String temporaryToken;
public UserAccount(String username, String temporaryToken) {
this.username = username;
this.temporaryToken = temporaryToken;
}
}
Here, username is eligible for serialization, while temporaryToken is excluded from default serialization.
Default Values After Deserialization
| Field Type |
Default Value for a Transient Field |
| Reference types, such as String |
null |
int |
0 |
double |
0.0 |
boolean |
false |
Do not rely on transient alone as a complete security mechanism. Carefully control what is serialized and where serialized data is stored.
12. Handling Static Fields
Static fields belong to the class rather than to individual objects. They are not part of the default serialized instance state. After deserialization, a static field's value comes from the currently loaded class and its runtime environment, not from the serialized object.
class Employee implements java.io.Serializable {
private static final long serialVersionUID = 1L;
static String company = "ABC Technologies";
private int employeeId;
private String name;
}
The company field is static, so its value is not saved as part of each Employee object's default serialized state.
13. Serialization of Object References
An object can contain references to other objects. When an object graph is serialized, Java can serialize the reachable serializable objects and preserve reference relationships within that stream. Every non-transient object in the graph must satisfy the serialization requirements, or serialization may fail with NotSerializableException.
import java.io.Serializable;
class Address implements Serializable {
private static final long serialVersionUID = 1L;
String city;
Address(String city) {
this.city = city;
}
}
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
}
In this example, both Person and Address implement Serializable, so the address object can be included when a Person instance is serialized.
14. Custom Serialization Using writeObject() and readObject()
Java allows a serializable class to customize its default serialization behavior using specially named private methods: writeObject(ObjectOutputStream) and readObject(ObjectInputStream). These methods must use the required signatures for Java serialization to recognize them.
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Product implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private double price;
Product(String name, double price) {
this.name = name;
this.price = price;
}
private void writeObject(ObjectOutputStream out)
throws IOException {
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
if (price < 0) {
throw new IOException("Invalid product price.");
}
}
}
defaultWriteObject() and defaultReadObject() preserve the normal default field processing. Custom methods can add validation or handle special requirements, but they must be designed carefully to maintain compatibility and security.
15. Understanding Externalizable
Externalizable is an interface that allows a class to define its own serialization format. Unlike default Serializable processing, it requires the class to implement writeExternal() and readExternal().
| Serializable |
Externalizable |
| Default field serialization is provided by Java. |
The class defines what data is written and read. |
| Does not require serialization methods to be implemented. |
Requires writeExternal() and readExternal(). |
| Offers less direct control over the serialized format. |
Offers more control over the format and ordering of data. |
| Custom behavior can be added using serialization hooks. |
Custom read/write behavior is central to the mechanism. |
Externalizable classes generally need an accessible public no-argument constructor for the deserialization process. Use Externalizable only when its additional control is useful.
16. Common Exceptions in Serialization
| Exception |
Cause |
Solution |
NotSerializableException |
An object being serialized, or an object in its graph, is not serializable. |
Make the required class serializable, exclude an appropriate field, or use a different storage format. |
InvalidClassException |
Class compatibility or serialization version checks fail. |
Review serialVersionUID and serialized-form compatibility. |
ClassNotFoundException |
The class needed to reconstruct the object cannot be found. |
Ensure the compatible class is available on the runtime classpath. |
IOException |
A stream or file operation fails. |
Check file paths, permissions, storage availability, and stream integrity. |
EOFException |
The input reaches the end unexpectedly, such as when no complete object remains to be read. |
Verify the file contains a complete serialized object and handle end-of-file conditions appropriately. |
StreamCorruptedException |
The input does not have a valid expected serialization-stream structure. |
Verify the correct file and stream format are being used and that the data is not corrupted. |
17. Security Best Practices
Java native deserialization can be dangerous when an application reads data from untrusted sources. Deserializing hostile input can trigger unexpected object construction or behavior through classes available to the application.
- Avoid deserializing untrusted data: Prefer safer, explicitly defined formats such as JSON when native Java serialization is unnecessary.
- Use an ObjectInputFilter: Where native deserialization is required, configure filters to restrict allowed classes, graph depth, references, array sizes, and stream size.
- Validate data: Check reconstructed object values before using them in business logic.
- Protect stored files: Apply suitable file permissions and protect sensitive serialized data.
- Do not store secrets casually: Passwords, authentication tokens, and confidential fields should not be included in serialized state unless there is a well-designed security requirement.
- Keep dependencies updated: Review libraries and classes that may be involved in deserialization.
- Use integrity protection where appropriate: If serialized data is transferred or stored externally, consider authentication and integrity checks in addition to access controls.
Example: Restricting Deserialization With ObjectInputFilter
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
public class SafeReadExample {
public static void main(String[] args) {
try (ObjectInputStream in = new ObjectInputStream(
new FileInputStream("student.ser"))) {
in.setObjectInputFilter(info -> {
if (info.depth() > 10
|| info.references() > 100
|| info.streamBytes() > 1_000_000
|| info.arrayLength() > 10_000) {
return ObjectInputFilter.Status.REJECTED;
}
Class<?> type = info.serialClass();
if (type == null) {
return ObjectInputFilter.Status.UNDECIDED;
}
if (type == Student.class
|| type == String.class) {
return ObjectInputFilter.Status.ALLOWED;
}
return ObjectInputFilter.Status.REJECTED;
});
Object value = in.readObject();
if (value instanceof Student) {
System.out.println(value);
}
} catch (IOException | ClassNotFoundException e) {
System.out.println("Unable to read object: "
+ e.getMessage());
}
}
}
Important: This example assumes that the Student class is available. Adapt the allowlist to the actual object graph, and test it carefully. An allowlist must include every required serializable class, including permitted nested types. Filtering reduces risk but does not make arbitrary untrusted deserialization automatically safe.
18. Common Mistakes and Troubleshooting
| Problem |
Why It Happens |
How to Fix It |
| Class does not implement Serializable |
Java cannot apply default serialization to that object. |
Implement Serializable where appropriate or choose another format. |
| A nested object is not serializable |
A field references an object that does not satisfy serialization requirements. |
Review the complete object graph and decide whether to serialize, exclude, or transform the field. |
| Deserialization fails after a code update |
The serialized data and current class definition may be incompatible. |
Review version identifiers and compatibility requirements; migrate old data if needed. |
| Transient field becomes null or zero |
The field was excluded from default serialization. |
Reinitialize it or restore it through appropriate custom logic. |
| File cannot be found |
The relative path points to a different working directory. |
Verify the actual file path and application working directory. |
| Text editor shows unreadable content |
Java serialization uses a binary format. |
Read the file using ObjectInputStream, not a character reader. |
19. Real-World Applications
- Object persistence: Saving simple Java object state for later retrieval.
- Legacy application integration: Sharing object data between compatible Java systems that use the same serialization format.
- Distributed systems: Transferring object data in environments that explicitly support Java serialization.
- Caching: Storing selected object representations where the format and security model are suitable.
- Testing: Creating test fixtures or sample serialized objects for controlled environments.
Native Java serialization is not always the best choice for long-term persistence or communication across different programming languages. JSON, Protocol Buffers, or other structured formats may be more appropriate when interoperability, schema evolution, or security is important.
20. Practical Exercises
- Create an Employee class with employee ID, name, and salary. Serialize an object to a file and deserialize it.
- Add a transient field named
temporaryPassword. Check its value after deserialization.
- Create a Department class and make Employee contain a Department object. Test serialization when both classes implement Serializable.
- Remove Serializable from a nested class and observe the resulting exception.
- Declare an explicit
serialVersionUID and investigate class compatibility by changing the class definition.
- Implement custom
writeObject() and readObject() methods to validate a field.
- Configure an ObjectInputFilter to allow only the classes needed by a sample application.
21. Interview Questions and Answers
Q1. What is serialization in Java?
Serialization is the process of converting an object's eligible state into a byte stream for storage or transfer.
Q2. What is deserialization?
Deserialization reads a serialized byte stream and reconstructs an object from it.
Q3. Which interface is used for default Java serialization?
The java.io.Serializable marker interface.
Q4. Which classes perform serialization and deserialization?
ObjectOutputStream writes objects, while ObjectInputStream reads objects.
Q5. What is serialVersionUID?
It is a version identifier used to help check compatibility between a serialized stream and the current class definition.
Q6. What is the purpose of transient?
The transient keyword excludes an instance field from default serialization.
Q7. Are static variables serialized by default?
No. Static variables belong to the class and are not part of the default serialized instance state.
Q8. What happens if an object contains a non-serializable field?
Default serialization generally fails with NotSerializableException unless the field is appropriately excluded or handled through a custom strategy.
Q9. What is the difference between Serializable and Externalizable?
Serializable supports default field serialization and optional custom hooks. Externalizable requires explicit implementation of the read and write operations.
Q10. Can serialization preserve references between objects?
Yes. Java serialization can preserve reference relationships within the serialized object graph.
Q11. Why can deserialization fail after a class update?
The serialized data may no longer be compatible with the updated class, including differences involving serialVersionUID or the serialized form.
Q12. Is Java native deserialization safe for untrusted input?
No. It can expose applications to serious security risks. Avoid it for untrusted data where possible, and use carefully configured filters and validation if it is required.
22. Quick Revision Checklist
- Serialization converts eligible object state into bytes.
- Deserialization reconstructs an object from a byte stream.
Serializable is a marker interface.
ObjectOutputStream.writeObject() writes an object.
ObjectInputStream.readObject() reads an object.
serialVersionUID helps check class compatibility.
transient excludes a field from default serialization.
- Static fields are not included in default serialized instance state.
- Nested objects must satisfy serialization requirements.
- Use try-with-resources to close streams reliably.
- Validate class compatibility and handle serialization exceptions.
- Never deserialize untrusted data without a suitable security strategy.
23. Conclusion
Object serialization and deserialization are important Java concepts for saving and reconstructing object state. By understanding Serializable, ObjectOutputStream, ObjectInputStream, transient fields, serialVersionUID, custom serialization, and exception handling, developers can use these mechanisms more effectively. Always consider compatibility, security, and whether a structured alternative is better for the application.
Learn Core Java with JustAcademy
Explore the course and continue building your Java programming skills through these resources: